package io;

import info.Review;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.jdom2.DataConversionException;
import org.jdom2.Element;
import org.jdom2.JDOMException;

/**
 * This class provides access to Review data for the peer review system.
 * 
 * @author Lun-Wei Chang
 * @author Aaron Chen
 * 
 * @version March 11, 2014
 * 
 */
public class ReviewAccess extends AbstractAccess {
  
  /** Name of the XML's file. */
  private static final String XML_FILE = "Review.xml";
  
  /** A constant for the ID attribute in the XML file. */
  private static final String ATTRIBUTE_ID = "id";
  /** A constant for the review id element in the XML file. */
  private static final String NODE_REVID = "ReviewID";
  /** A constant for the review name element in the XML file. */
  private static final String NODE_REVNAME = "ReviewName";
  /** A constant for the conference title in the XML file. */
  private static final String NODE_CONFERENCE_TITLE = "ConferenceTitle";
  /** A constant for the manuscript author in the XML file. */
  private static final String NODE_MANUSCRIPT_AUTHOR = "ManuscriptAuthor";
  /** A constant for the manuscript title in the XML file. */
  private static final String NODE_MANUSCRIPT_TITLE = "ManuscriptTitle";
  /** A constant for the confidential comments element in the XML file. */
  private static final String NODE_CONFCOM = "ConfidentialComments";
  /** A constant for the summary comments element in the XML file. */
  private static final String NODE_SUMCOM = "SummaryComments";
  /** A constant for the summary rating element in the XML file. */
  private static final String NODE_SUMRATE = "SummaryRating";
  /** A constant for the question 1 element in the XML file. */
  private static final String NODE_QUESTIONS = "QuestionAnswers";
  /** A constant prefix for each question element in the XML file. */
  private static final String NODE_QUESTION_PREFIX = "Q";
  
  
  /** 
   * The constructor to instantiate the review objects.
   * @throws IOException If there is a problem establishing connectivity to the file.
   * @throws JDOMException If the file cannot be correctly parsed. 
   */
  public ReviewAccess() throws IOException, JDOMException {
    super(XML_FILE);
  }


  /**
   * Returns all the reviews.
   * 
   * @return A list of reviews.
   */
  public List<Review> getAllReviews() {
    final List<Review> reviews = new ArrayList<Review>();

    final Element rootnode = getDoc().getRootElement();
    final List<Element> list = rootnode.getChildren();
    for (Element e : list) {
      reviews.add(dataToReview(e));
    }
    
    return reviews;
  }

  /**
   * Returns a specific Review.
   * 
   * @param the_id The identification number unique to each Review.
   * @return A single Review.
   */
  public Review getReview(final long the_id) {
    Review r = null;
    final Element rootnode = getDoc().getRootElement();
    final List<Element> list = rootnode.getChildren(NODE_REVID);
    for (Element e : list) {
      try {
        if (e.getAttribute(ATTRIBUTE_ID).getLongValue() == the_id) {
          r = dataToReview(e);
          break;
        }
      } catch (final DataConversionException err) {
        err.printStackTrace();
      }
    }
    return r;
  }

  /**
   * Writes a new Review entry into the database.
   * 
   * @param the_r A new Review.
   * @return true if the operation was a success; false otherwise.
   */
  public boolean createReview(final Review the_r) {
    boolean result = true;

    final Element rootnode = getDoc().getRootElement();
    final Element e = reviewToData(the_r, false);
    rootnode.addContent(e);

    // If the write operation has failed, then remove the node so that
    // subsequent write operations do not include this node.
    result = writeToDatabase();
    if (!result) {
      e.detach();
    }

    return result;
  }

  /**
   * Writes over a pre-existing review in the database.
   * 
   * @param the_r The review data to write into the database.
   * @return true if the review to edit was found and overwritten; false
   *         otherwise.
   */
  public boolean editReview(final Review the_r) {
    boolean result = false;

    final Element rootnode = getDoc().getRootElement();
    final List<Element> list = rootnode.getChildren();
    Element old_element = null;
    final Element new_element = reviewToData(the_r, true);
    for (Element e : list) {
      if (Long.parseLong(e.getAttributeValue(ATTRIBUTE_ID)) == the_r.getID()) {
        old_element = e.detach();
        rootnode.addContent(new_element);
        result = true;
        break;
      }
    }

    // If the write operation has failed, then remove the node so that
    // subsequent write operations do not include this node.
    if (result) {
      result = writeToDatabase();
      if (!result) {
        rootnode.addContent(old_element);
        new_element.detach();
      }
    }

    return result;
  }

  /**
   * Deletes a review from the database.
   * @param the_id The ID of the review.
   * @return true if the operation was a success; false otherwise.
   */
  public boolean deleteReview(final long the_id) {
    boolean result = false;

    final Element rootnode = getDoc().getRootElement();
    final List<Element> list = rootnode.getChildren();
    Element element = null;
    for (Element e : list) {
      if (Long.parseLong(e.getAttributeValue(ATTRIBUTE_ID)) == the_id) {
        element = e.detach();
        result = true;
        break;
      }
    }
    if (result) {
      result = writeToDatabase();
      if (!result) {
        rootnode.addContent(element);
      }
    }

    return result;
  }

  /**
   * Translates the XML element data into a Review object.
   * 
   * @param the_e The element that contains Review data.
   * @return a Review object.
   */
  private Review dataToReview(final Element the_e) {
    final long id = Long.parseLong(the_e.getAttributeValue(ATTRIBUTE_ID));
    
    final List<String> fields = new ArrayList<String>();
    fields.add(the_e.getChildText(NODE_REVNAME));
    fields.add(the_e.getChildText(NODE_CONFERENCE_TITLE));
    fields.add(the_e.getChildText(NODE_MANUSCRIPT_AUTHOR));
    fields.add(the_e.getChildText(NODE_MANUSCRIPT_TITLE));
    
    final Element answers_element = the_e.getChildren(NODE_QUESTIONS).get(0);
    final List<Element> answer_nodes = answers_element.getChildren();
    final int[] answers = new int[answer_nodes.size()];
    final Iterator<Element> itr = answer_nodes.iterator();
    String str = null;
    for (int i = 0; i < answer_nodes.size(); i++) {
      str = itr.next().getText();
      if (isNonemptyString(str)) {
        answers[i] = Integer.parseInt(str);
      }
    }

//    final Element categories_element = the_element.getChildren(NODE_CATEGORY).get(0);
//    final List<Element> categories_list = categories_element.getChildren(NODE_SUB_CAT);
//    final List<String> categories = new ArrayList<String>();
//    for (Element e : categories_list) {
//      categories.add(e.getText());
//    }
    
    int summary = 0;
    str = the_e.getChildText(NODE_SUMRATE);
    if (isNonemptyString(str)) {
      summary = Integer.parseInt(str);
    }
    
    final Review r = new Review(id,
                                fields,
                                the_e.getChildText(NODE_CONFCOM),
                                answers,
                                summary,
                                the_e.getChildText(NODE_SUMCOM));
    return r;
  }

  /**
   * Translates the Review object into XML element data.
   * 
   * @param the_r The Review object.
   * @param the_edit True if this is an edit operation; false if it is a new
   *          entry.
   * @return an element containing the Review data.
   */
  private Element reviewToData(final Review the_r, final boolean the_edit) {
    final Element e = new Element(NODE_REVID);

    if (!the_edit) {
      final long id = getNextID();
      e.setAttribute(ATTRIBUTE_ID, Long.toString(id));
      the_r.setID(id);
    } else {
      e.setAttribute(ATTRIBUTE_ID, Long.toString(the_r.getID()));
    }
    
    e.addContent(new Element(NODE_REVNAME).setText(the_r.getReviewerName()));
    e.addContent(new Element(NODE_CONFERENCE_TITLE).setText("" + the_r.getConferenceID()));
    e.addContent(new Element(NODE_MANUSCRIPT_AUTHOR).setText(the_r.getManuscriptAuthor()));
    e.addContent(new Element(NODE_MANUSCRIPT_TITLE).setText(the_r.getManuscriptTitle()));
    
    e.addContent(new Element(NODE_CONFCOM).setText(the_r.getConfidentialComments()));
    
    final Element eq = new Element(NODE_QUESTIONS);
    final int[] answers = the_r.getQuestionaireRatings();
    for (int i = 0; i < answers.length; i++) {
      eq.addContent(
          new Element(NODE_QUESTION_PREFIX + i).setText(Integer.toString(answers[i])));
    }
    e.addContent(eq);
    
    e.addContent(
        new Element(NODE_SUMRATE).setText(Integer.toString(the_r.getSummaryRating())));
    e.addContent(new Element(NODE_SUMCOM).setText(the_r.getSummary()));
    
    return e;
  }
  
}
